package lol.clann.minecraft.springboot.api.bukkit.constant;

import com.ruoshui.utils.java.exception.ExceptionUtils;
import com.ruoshui.utils.java.reflect.ReflectUtils;
import com.ruoshui.utils.java.reflect.model.FieldFilter;
import lol.clann.minecraft.springboot.api.common.Invoker;
import lol.clann.minecraft.springboot.api.model.reflect.ConstructorInvoker;
import lol.clann.minecraft.springboot.api.model.reflect.FieldInvoker;
import lol.clann.minecraft.springboot.api.model.reflect.UnsafeConstructorInvoker;
import lol.clann.minecraft.springboot.bukkit.utils.NMSClasses;
import lombok.Getter;

import java.lang.reflect.Field;
import java.util.*;

/**
 *
 * @author pyz
 * @date 2019/5/3 3:24 PM
 */
public enum NBTTypeEnum {

    NBTTagByte("b ", byte.class, Byte.class),
    NBTTagByteArray("bs", byte[].class),
    NBTTagShort("s ", short.class, Short.class),
    NBTTagInt("i ", int.class, Integer.class),
    NBTTagIntArray("is", int[].class),
    NBTTagLong("l ", long.class, Long.class),
    NBTTagLongArray("ls", long[].class),
    NBTTagFloat("f ", float.class, Float.class),
    NBTTagDouble("d ", double.class, Double.class),
    NBTTagString("S ", String.class),
    NBTTagList("", List.class),
    NBTTagCompound("", Map.class);

    @Getter
    private final boolean support;
    @Getter
    private final Class[] valueTypes;
    @Getter
    private final Invoker constructor;
    @Getter
    private final FieldInvoker field;
    @Getter
    private final Class type;
    @Getter
    private final String identity;
    private static final Map<Class, NBTTypeEnum> classMap = new HashMap<>();
    private static final Map<Class, NBTTypeEnum> valueClassMap = new HashMap<>();
    private static final Map<String, NBTTypeEnum> identityMap = new HashMap<>();
    private static final Set<String> nbtClasses = new HashSet<>();
    private static final Set<String> nbtValueClasses = new HashSet<>();

    static {
        for (NBTTypeEnum nbtTypeEnum : NBTTypeEnum.values()) {
            if (!nbtTypeEnum.support) {
                continue;
            }
            nbtClasses.add(nbtTypeEnum.getType().getName());
            identityMap.put(nbtTypeEnum.getIdentity(), nbtTypeEnum);
            NBTTypeEnum.classMap.put(nbtTypeEnum.type, nbtTypeEnum);
            for (Class valueType : nbtTypeEnum.valueTypes) {
                valueClassMap.put(valueType, nbtTypeEnum);
                nbtValueClasses.add(valueType.getName());
            }
        }
    }

    NBTTypeEnum(String identity, Class... valueTypes) {
        try {
            Field f = NMSClasses.class.getDeclaredField(this.name());
            f.setAccessible(true);
            this.type = (Class) f.get(null);
            this.support = this.type != null;
            this.identity = !this.support ? null : identity;
            this.valueTypes = !this.support ? null : valueTypes;
            this.field = !this.support ? null : new FieldInvoker(ReflectUtils.getField(this.type, FieldFilter.create().setStatic(false).setType(this.valueTypes[0])));
            Invoker constructor = null;
            if (!this.support) {
                constructor = null;
            } else {
                try {
                    constructor = new ConstructorInvoker(this.type.getDeclaredConstructor());
                } catch (NoSuchMethodException x) {
//                    没有无惨构造函数,使用Unsafe类来实例化
                    constructor = new UnsafeConstructorInvoker(this.type);
                }
            }
            this.constructor = constructor;
        } catch (Throwable e) {
            ExceptionUtils.throwException(e);
            throw new RuntimeException();
        }
    }

    public boolean isInstance(Object nbtTag) {
        return this.getType().isInstance(nbtTag);
    }

    public boolean isInstanceValue(Object value) {
        for (Class valueType : this.getValueTypes()) {
            if (valueType == value.getClass()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 通过nbt类找NBTTypeEnum
     *
     * @param clazz
     * @return
     */
    public static NBTTypeEnum getByType(Class clazz) {
        return classMap.get(clazz);
    }

    /**
     * 通过nbt值类找NBTTypeEnum
     *
     * @param clazz
     * @return
     */
    public static NBTTypeEnum getByValueType(Class clazz) {
        if (List.class.isAssignableFrom(clazz)) {
            return NBTTypeEnum.NBTTagList;
        } else if (Map.class.isAssignableFrom(clazz)) {
            return NBTTypeEnum.NBTTagCompound;
        } else {
            return valueClassMap.get(clazz);
        }
    }

    public static NBTTypeEnum getByIdentity(String identity) {
        NBTTypeEnum type = identityMap.get(identity);
        return type;
    }

    public static boolean isNbtClass(Class clazz) {
        return nbtClasses.contains(clazz.getName());
    }

    public static boolean isNbt(Object nbtTag) {
        return nbtClasses.contains(nbtTag.getClass().getName());
    }

    public static boolean isNbtValue(Object value) {
        return nbtValueClasses.contains(value.getClass());
    }

}
